package database


import io.github.oshai.kotlinlogging.KotlinLogging
import org.apache.iotdb.isession.SessionDataSet
import org.apache.iotdb.isession.template.Template
import org.apache.iotdb.isession.util.Version
import org.apache.iotdb.session.Session
import org.apache.iotdb.session.template.MeasurementNode
import org.apache.iotdb.tsfile.file.metadata.enums.CompressionType
import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType
import org.apache.iotdb.tsfile.file.metadata.enums.TSEncoding
import org.apache.iotdb.tsfile.write.record.Tablet

private val logger = KotlinLogging.logger {  }

/** @author zh 2023-10-24
 *
 *  几个需要注意的地方：
 *  1、IotDb 属于DataMaster工厂， 创建由上层逻辑决定
 *  2、IotDb 应继承DataMaster的基本方法，可以考虑使用别名 typealias
 *  3、IotDb 数据类型需要若干扩展方法，统一接口需与其他数据库保持一致
 *
 **/

class IotDbProperty(
    override var id: String,
    override var name: String,
    override var ip: String,
    override var port: Int,
    override var username: String,
    override var password: String
) : DataProperty() {
    var fetchSize: Int = 1000
    var enableRPC:Boolean = false
    var version = Version.V_1_0

    override fun useProperty(): MutableMap<String, String> {
        return mutableMapOf(
            Pair("id", "iotdb"),
            Pair("name", "本地 iotdb 数据库"),
            Pair("ip", "127.0.0.1"),
            Pair("port", "6667"),
            Pair("username", "root"),
            Pair("password", "root"),
            Pair("fetchSize", "1000"),
            Pair("enableRPC", "false"),
            Pair("version", "V_1_0"),
        )
    }
}

class IotDbFactory(override val dataProperty: DataProperty) : DataMaster {
    private val iotSession: Session
    private val dp = dataProperty as IotDbProperty

    init {
        iotSession = Session.Builder()
            .host(dp.ip)
            .port(dp.port)
            .username(dp.username)
            .password(dp.password)
            .version(dp.version)
            .fetchSize(dp.fetchSize)
            .build()
        connect()
    }

    fun connect(): Boolean {
        return try {
            iotSession.open(dp.enableRPC)
            logger.info { "IotDb open successfully" }
            true
        }catch (e: Exception){
            logger.error { e.toString() }
            false
        }
    }

    fun createDataBase(name: String): Boolean {
        return try {
            iotSession.createDatabase(name)
            logger.info { "IotDb create database $name successfully" }
            true
        }catch (e: Exception){
            logger.error { e.toString() }
            false
        }
    }

    fun deleteDataBase(name: String) : Boolean {
        return try {
            iotSession.deleteDatabase(name)
            logger.info { "IotDb delete database $name successfully" }
            true
        }catch (e: Exception){
            logger.error { e.toString() }
            false
        }
    }

    fun createTemplate(name: String, nodes: List<MeasurementNode>) {
        try {
            val template = Template(name)
            nodes.forEach { template.addToTemplate(it) }
            iotSession.createSchemaTemplate(template)
            logger.info { "IotDb create template $name successfully" }
        }catch (e: Exception){
            logger.error { e.toString() }
        }
    }

    fun setTemplate(name: String, prefixPath:String) {
        try {
            iotSession.setSchemaTemplate(name, prefixPath)
            logger.info { "IotDb set template $name at $prefixPath successfully" }
        }catch (e: Exception){
            logger.error { e.toString() }
        }
    }

    fun unSetTemplate(name: String, prefixPath:String) {
        try {
            iotSession.unsetSchemaTemplate(prefixPath, name)
            logger.info { "IotDb unset template $name at $prefixPath successfully" }
        }catch (e: Exception){
            logger.error { e.toString() }
        }
    }

    fun dropTemplate(name: String) {
        try {
//            val tPaths = iotSession.showPathsTemplateSetOn(name)
//            tPaths.forEach { iotSession.unsetSchemaTemplate(it, name) }
            iotSession.dropSchemaTemplate(name)
            logger.info { "IotDb drop template $name successfully" }
        }catch (e: Exception){
            logger.error { e.toString() }
        }
    }

    fun checkTimeSeries(path: String): Boolean {
        return try {
            logger.info { "IotDb check TimeSeries $path " }
            iotSession.checkTimeseriesExists(path)
        }catch (e: Exception){
            false
        }
    }

    fun createTimeSeries(path:String, dataType: TSDataType, encoding: TSEncoding, compressor: CompressionType) : Boolean {
        return try {
            iotSession.createTimeseries(path, dataType, encoding, compressor)
            logger.info { "IotDb create TimeSeries $path " }
            true
        }catch (e: Exception){
            logger.error { e.toString() }
            false
        }
    }

    fun createTimeSeriesUsingTemplate(devicePathList: List<String>){
        try {
            iotSession.createTimeseriesUsingSchemaTemplate(devicePathList)
            logger.info { "IotDb create TimeSeries Using pathList " }
        }catch (e: Exception){
            logger.error { e.toString() }
        }
    }

    fun createMultiTimeSeries(paths: List<String>, dataTypes: List<TSDataType>,
                              encodings: List<TSEncoding>, compressors: List<CompressionType>) : Boolean {
        return try {
            iotSession.createMultiTimeseries(paths, dataTypes, encodings, compressors,
                null, null, null, null)
            logger.info { "IotDb create MultiTimeSeries with length of ${paths.size} and first element of ${paths[0]}" }
            true
        }catch (e: Exception){
            logger.error { e.toString() }
            false
        }
    }

    // 注意，这个prefixPath 并不是时间序列， 时间序列要到measurement这一层级
    fun createAlignedTimeSeries(prefixPath:String, measurements: List<String>,
                                dataTypes: List<TSDataType>, encodings: List<TSEncoding>,
                                compressors: List<CompressionType>) : Boolean{
        return try {
            iotSession.createAlignedTimeseries(prefixPath, measurements, dataTypes, encodings, compressors,
                null,null,null)
            logger.info { "IotDb create aligned TimeSeries with prefixPath $prefixPath " }
            true
        }catch (e: Exception){
            logger.error { e.toString() }
            false
        }
    }

    fun deleteTimeSeries(path: String) : Boolean {
        return try {
            iotSession.deleteTimeseries(path)
            logger.info { "IotDb delete TimeSeries $path " }
            true
        }catch (e: Exception){
            logger.error { e.toString() }
            false
        }
    }

    fun deleteMultiTimeSeries(paths: List<String>) : Boolean {
        return try {
            iotSession.deleteTimeseries(paths)
            logger.info { "IotDb delete MultiTimeSeries with length of ${paths.size} and first element of ${paths[0]}" }
            true
        }catch (e: Exception){
            logger.error { e.toString() }
            false
        }
    }

    fun insertRecord(prefixPath: String, time: Long, measurements: List<String>, dataTypes: List<TSDataType>,
                     values: List<Any>, aligned: Boolean = false) : Boolean {
        return try {
            if (aligned){
                iotSession.insertAlignedRecord(prefixPath, time, measurements, dataTypes, values)
            } else {
                iotSession.insertRecord(prefixPath, time, measurements, dataTypes, values)
            }
            logger.info { "IotDb insert one record of $prefixPath and the time is $time with aligned = $aligned" }
            true
        }catch (e: Exception){
            logger.error { e.toString() }
            false
        }
    }

    fun insertMultiRecords(paths: List<String>, times: List<Long>, measurementList: List<List<String>>,
                           dataTypeList: List<List<TSDataType>>, valueList: List<List<Any>>,
                           aligned: Boolean = false) {
        try {
            if (aligned){
                iotSession.insertAlignedRecords(paths, times, measurementList, dataTypeList, valueList)
            } else {
                iotSession.insertRecords(paths, times, measurementList, dataTypeList, valueList)
            }
            logger.info { "IotDb delete MultiTimeSeries with length of ${paths.size} and first element of ${paths[0]}" }
        }catch (e: Exception){
            logger.error { e.toString() }
        }
    }

    fun deleteRecord(path: String, time: Long) : Boolean {
        return try {
            iotSession.deleteData(path, time)
            logger.info { "IotDb delete Record of path = $path at time = $time " }
            true
        }catch (e: Exception){
            logger.error { e.toString() }
            false
        }
    }

    fun deleteMultiRecords(paths: List<String>, endTime: Long) : Boolean {
        return try {
            iotSession.deleteData(paths, endTime)
            logger.info { "IotDb delete MultiRecords with length of ${paths.size} and endTime of $endTime " }
            true
        }catch (e: Exception){
            logger.error { e.toString() }
            false
        }
    }

    fun deleteMultiRecords(paths: List<String>, startTime: Long, endTime: Long) {
        try {
            iotSession.deleteData(paths, startTime, endTime)
            logger.info { "IotDb delete MultiTimeSeries with length of ${paths.size} and range of [$startTime, $endTime]" }
        }catch (e: Exception){
            logger.error { e.toString() }
        }
    }

    fun queryRecord(path : String) : TimeSeries? {
        try {
            logger.info { "IotDb query TimeSeries $path " }
            return iotSession.executeRawDataQuery(listOf(path), 0, 9999999999999, 500).measurementValue()
        }catch (e: Exception){
            logger.error { e.toString() }
            return null
        }
    }

    fun queryTimeRange(paths: List<String>, startTime:Long, endTime: Long, timeOut: Long = 5000) : TimeSeries? {
        try {
            logger.info { "IotDb query TimeSeries with first element ${paths[0]} and range of [$startTime, $endTime]" }
            return iotSession.executeRawDataQuery(paths, startTime, endTime, timeOut).measurementValue()
        }catch (e: Exception){
            logger.error { e.toString() }
            return null
        }
    }

    fun queryLastRecord(paths: List<String>, endTime: Long, timeOut: Long = 5000) : TimeSeries? {
        try {
            logger.info { "IotDb query TimeSeries with first element ${paths[0]} and lastTime of $endTime" }
            return iotSession.executeLastDataQuery(paths, endTime, timeOut).measurementValue()
        }catch (e: Exception){
            logger.error { e.toString() }
            return null
        }
    }

    fun queryRawSql(sql: String) : TimeSeriesList? {
        try {
            val result = iotSession.executeQueryStatement(sql).deviceValue()
            result.name = sql
            logger.info { "IotDb execute query of sql $sql " }
            return result
        }catch (e: Exception){
            logger.error { e.toString() }
            return null
        }
    }

    fun commandRawSql(sql: String) {
        try {
            iotSession.executeNonQueryStatement(sql)
            logger.info { "IotDb execute command of sql $sql " }
        }catch (e: Exception){
            logger.error { e.toString() }
        }
    }

    fun showDataBases() {
        try {
            iotSession.executeQueryStatement("show databases root.**").print()
            logger.info { "IotDb show databases successfully" }
        } catch (e: Exception){
            logger.error { e.toString() }
        }
    }

    fun showTimeSeries(path:String) {
        try {
            iotSession.executeQueryStatement("show timeseries $path").print()
            logger.info { "IotDb show timeseries of $path successfully" }
        } catch (e: Exception){
            logger.error { e.toString() }
        }
    }

    fun queryDataBases(): List<String>? {
        try {
            logger.info { "IotDb query database list successfully" }
            return iotSession.executeQueryStatement("show databases root.**").resultList()
        } catch (e: Exception){
            logger.error { e.toString() }
            return null
        }
    }

    fun queryTimeSeries(path:String) : List<String>? {
        try {
            logger.info { "IotDb show timeseries of $path successfully" }
            return iotSession.executeQueryStatement("show timeseries $path").resultList()
        } catch (e: Exception){
            logger.error { e.toString() }
            return null
        }
    }

    fun queryTemplates() : List<String>? {
        try {
            logger.info { "IotDb show all templates successfully" }
            return iotSession.showAllTemplates()
        }catch (e: Exception){
            logger.error { e.toString() }
            return null
        }
    }

    fun queryPathsOfTemplate(templateName:String) : List<String>? {
        try {
            logger.info { "IotDb show all templates of name : $templateName successfully" }
            return iotSession.showPathsTemplateSetOn(templateName)
        }catch (e: Exception){
            logger.error { e.toString() }
            return null
        }
    }

    fun countDataBases(): Int {
        return try {
            logger.info { "IotDb count databases successfully" }
            iotSession.executeQueryStatement("count databases").count()
        } catch (e: Exception){
            logger.error { e.toString() }
            0
        }
    }

    fun countTimeSeries(path:String): Int {
        var result = 0
        try {
            logger.info { "IotDb count timeseries $path successfully" }
            result = iotSession.executeQueryStatement("count timeseries $path").count()
        } catch (e: Exception){
            logger.error { e.toString() }
        }
        return result
    }

    fun insertTablets(tablet: Tablet) {
        try {
            iotSession.insertTablet(tablet)
            logger.info { "IotDb check databases successfully" }
        } catch (e: Exception){
            logger.error { e.toString() }
        }
    }
}

fun SessionDataSet.print() {
    this.fetchSize = 1024
    println("****************************************************")
    this.columnNames.forEach {
        print("$it   ")
    }
    println()
    println("----------------------------------------------------")
    this.columnTypes.forEach {
        print("$it   ")
    }
    println()
    println("----------------------------------------------------")
    while (this.hasNext()){
        println(this.next())
    }
    println("****************************************************")
}

fun SessionDataSet.count() : Int {
    var result = 0
    this.fetchSize = 1
    while (this.hasNext()){
        result = this.next().fields[0].stringValue.toInt()
    }
    return result
}

fun SessionDataSet.measurementValue() : TimeSeries {
    val name = this.columnNames[1]
    val vals = mutableMapOf<Long, Float>()
    this.fetchSize = 1024
    while (this.hasNext()){
        val v = this.next()
        vals[v.timestamp] = v.fields[0].floatV
    }
    return TimeSeries(name, vals)
}

fun SessionDataSet.deviceValue() : TimeSeriesList {
    val vals = mutableListOf<TimeSeries>()
    this.fetchSize = 1024
    this.columnNames.forEachIndexed { index, name ->
        if (index > 0) vals.add(TimeSeries(name, mutableMapOf()))
    }
    while (this.hasNext()) {
        val v = this.next()
        v.fields.forEachIndexed { index, field ->
            if(field.dataType !== null) vals[index].values[v.timestamp] = field.floatV
        }
    }
    return TimeSeriesList("", vals)
}

fun List<String>.print() {
    print("(")
    this.forEach { print("$it, ") }
    println(")")
}

fun SessionDataSet.resultList() : List<String> {
    val result = mutableListOf<String>()
    while (this.hasNext()) result.add(this.next().fields[0].stringValue)
    return result.toList()
}

fun main() {

}